package com.triplelink.business.impl.rush;

import java.lang.reflect.Field;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import com.triplelink.annotations.DatatypeProperty;
import com.triplelink.annotations.Id;
import com.triplelink.annotations.ObjectProperty;
import com.triplelink.annotations.Property;
import com.triplelink.enums.Datatype;
import com.triplelink.exception.DatatypePropertyNotMatchException;
import com.triplelink.exception.TripleLinkException;
import com.triplelink.to.steak.ParserStackContainerTO;
import com.triplelink.util.DateTimeUtil;
import com.triplelink.util.DateUtil;
import com.triplelink.util.EntityUtil;

public class EntityGeneretor
{
	private String id;
	private Map<String, Map<String, List<String>>> instances;
	private Object entity;
	private Stack<ParserStackContainerTO> stack;
	
	private EntityGeneretor(String id, Map<String, Map<String, List<String>>> instances, Class<?> entityClass)
	{
		this.id = id;
		this.instances = instances;
		this.stack = new Stack<ParserStackContainerTO>();
		
		try
		{
			this.entity = entityClass.newInstance();
		}
		catch (InstantiationException e)
		{
			throw new TripleLinkException(e);
		}
		catch (IllegalAccessException e)
		{
			throw new TripleLinkException(e);
		}
	}
	
	@SuppressWarnings("unchecked")
	public static <T extends Object> T generate(String id, Map<String, Map<String, List<String>>> instances, Class<T> entityClass)
	{
		return (T) new EntityGeneretor(id, instances, entityClass).parse();
	}
	
	private Object parse()
	{
		try
		{
			String id = this.id;
			EntityUtil.putId(entity, id);
			push(entity, instances.get(id));
			
			while (!stack.isEmpty())
			{
				ParserStackContainerTO item = stack.pop();
				
				for (Field field : item.getClasse().getDeclaredFields())
				{
					field.setAccessible(true);
					
					Id semanticId = field.getAnnotation(Id.class);
					
					if (semanticId != null)
					{
						continue;
					}
					
					putValue(field, item.getEntity(), item.getInstance());
				}
			}
		}
		catch (IllegalArgumentException e)
		{
			throw new TripleLinkException(e);
		}
		catch (IllegalAccessException e)
		{
			throw new TripleLinkException(e);
		}
		catch (InstantiationException e)
		{
			throw new TripleLinkException(e);
		}
		
		return entity;
	}
	
	private void putValue(Field field, Object entity, Map<String, List<String>> instance) throws IllegalArgumentException, IllegalAccessException, InstantiationException
	{
		field.setAccessible(true);

		Property property = field.getAnnotation(Property.class);
		
		if (property == null) {
			
			return;
		}
		
		List<String> values = instance.get(property.uri());
		
		if (values == null || values.isEmpty())
		{
			return;
		}
		
		if (field.getType().isAssignableFrom(String.class))
		{
			DatatypeProperty datatypeProperty = field.getAnnotation(DatatypeProperty.class);
			
			if (datatypeProperty != null)
			{
				if (datatypeProperty.range() == Datatype.STRING)
					field.set(entity, values.get(0));
			}
			else
			{
				field.set(entity, values.get(0));
			}
		}
		else if(field.getType().isAssignableFrom(Date.class))
		{
			DatatypeProperty datatypeProperty = field.getAnnotation(DatatypeProperty.class);
			
			if (datatypeProperty != null)
			{
				if (datatypeProperty.range() == Datatype.DATE)
					field.set(entity, DateUtil.parse(values.get(0)));
				else if (datatypeProperty.range() == Datatype.DATE_TIME)
					field.set(entity, DateTimeUtil.parse(values.get(0)));
				else
					throw new DatatypePropertyNotMatchException(field, datatypeProperty);
			}
			else
			{
				field.set(entity, DateTimeUtil.parse(values.get(0)));
			}
		}
		else
		{
			ObjectProperty semanticJoin = field.getAnnotation(ObjectProperty.class);
			
			String id = values.get(0);
			
			Object entityObj = field.getType().newInstance();
			
			EntityUtil.putId(entityObj, id);
			
			if (semanticJoin != null && semanticJoin.cascadeSelect()) {
				
				push(entityObj, instances.get(id));
			}
		}
		
		instance.put(property.uri(), values);
	}
	
	private void push(Object entity, Map<String, List<String>> instance)
	{
		Class<?> currentClass = entity.getClass();
		
		while (currentClass != null)
		{
			stack.push(new ParserStackContainerTO(entity, currentClass, instance));
			
			currentClass = currentClass.getSuperclass();
		}
	}
}
